package logic;

import javafx.util.Pair;
import lombok.Data;
import view.Main;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: SmartBoy
 * @Date: 2021/07/26/19:33
 * @Description:
 */
@Data
public class FileSystem {

    private static FileSystem instance = new FileSystem();

    //初始化
    private FileSystem() {
        initBlock();
        initFS();
    }

    //饿汉式单例模式
    public static FileSystem getInstance() {
        return instance;
    }

    //根目录
    public MyFile root = new MyFile(DATA.PATHROOT, null, 8, 2, 0, DATA.PATHROOT, null, new CopyOnWriteArrayList<>(), true, false, null);

    //当前所在目录
    public MyFile parent = root;

    //文件/目录查询表
    public Map<String, MyFile> fileMap = new HashMap<>();

    //已打开文件列表
    public Map<String, MyFile> fileOpened = new HashMap<>();

    //fat表
    public int[] fat = new int[128];

    //磁盘块表
    public Block[] blocks = new Block[128];

    //初始化磁盘块
    private void initBlock(){
        for (int i = 0; i < DATA.BLOCK_MAX; i++)
            blocks[i] = new Block().setId(i);

        char[] fatMap = blocks[0].data;
        fatMap[0] = 1;
        fatMap[1] = (char) -1;
        fatMap[2] = (char) -1;
        fat[0] = 1;
        fat[1] = -1;
        fat[2] = -1;
        blocks[0].setNext(1);
        blocks[1].setNext(-1);
        blocks[2].setNext(-1);
        char[] data = blocks[2].data;
        for (int i = 0; i < DATA.BLOCK_SIZE; i += 16) {
            data[i] = '$';//空文件夹
        }
    }

    //初始化文件系统内容
    private void initFS(){

        fileMap.put("Root",root);

        RespBean respBean3 = createFile("000", "txt", 4);
        MyFile file0 = (MyFile) respBean3.getObj();
        openFile(file0);
        writeFile(file0, "This is 000.txt!", true);
        closeFile(file0);

        RespBean respBean4 = createFile("001", "txt", 4);
        MyFile file1 = (MyFile) respBean4.getObj();
        openFile(file1);
        writeFile(file1, "This is 001.txt!", true);
        modifyFile(file1,"001","txt",1);
        closeFile(file1);

        RespBean respBean = createDirectory("bin");
        MyFile bin = (MyFile) respBean.getObj();
        openDirectory(bin);
        RespBean respBean1 = createFile("test", "txt", 4);
        MyFile test = (MyFile) respBean1.getObj();
        openFile(test);
        writeFile(test, "this is a test file!", true);
        closeFile(test);
        openDirectory(root);

        RespBean respBean2 = createDirectory("home");
        MyFile home = (MyFile) respBean2.getObj();
        openDirectory(home);
        RespBean respBean5 = createFile("test1", "txt", 4);
        MyFile test1 = (MyFile) respBean5.getObj();
        openFile(test1);
        writeFile(test1, "this is a test1 file!", true);
        closeFile(test1);

        openDirectory(root);
    }


    //查找空闲磁盘块号
    private int findFreeNode() {
        for (int i = DATA.BLOCK_MIN; i <= DATA.BLOCK_MAX; i++) {
            if (blocks[i].next == 0) {
                return i;
            }
        }
        return -1;
    }

    //将文件信息添加到父目录磁盘块中
    private RespBean writeMyFileToParentBlock(MyFile parent, MyFile file) {
        int init = parent.getInitBlockId();
        Block block = blocks[init];
        char[] data = block.data;
        do {
            for (int i = 0; i < DATA.BLOCK_SIZE; i += 16) {
                if (data[i] == '$') {
                    writeMyFileBlock(file, data, i);
                    file.setCoordinate(new Pair<>(block.id, i));
                    return RespBean.success("成功");
                }
            }
            if (block.next == -1) break;
            block = blocks[block.next];
            data = block.data;
        }
        while (true);
        int node = findFreeNode();
        if (node == -1) return RespBean.error("创建文件失败，磁盘空间已满！");
        else {
            //新开一个磁盘块
            block.next = node;
            blocks[node].next = DATA.FILE_END;
            fat[block.id] = node;
            fat[node] = DATA.FILE_END;
            blocks[block.id / DATA.BLOCK_SIZE].data[block.id % DATA.BLOCK_SIZE] = (char) node;
            blocks[node / DATA.BLOCK_SIZE].data[node % DATA.BLOCK_SIZE] = (char) DATA.FILE_END;
            data = blocks[node].data;
            writeMyFileBlock(file, data, 0);
            file.setCoordinate(new Pair<>(blocks[node].id, 0));
            for (int i = 16; i < DATA.BLOCK_SIZE; i += 16) {
                data[i] = '$';
            }
        }
        return RespBean.success("成功！");
    }

    //填写文件/目录名到磁盘块
    private void writeMyFileBlock(MyFile file, char[] data, int index) {
        char[] name = file.getName().toCharArray();
        String fileType = file.getType();
        char[] type = "".toCharArray();
        if (fileType != null) type = fileType.toCharArray();
        for (int i = 0; i < DATA.NAME_MAXLENGTH; i++) {
            if (i < name.length)
                data[index + i] = name[i];
            else
                data[index + i] = '$';
        }
        for (int i = 0; i < DATA.TYPE_MAXLENGTH; i++) {
            if (i < type.length)
                data[index + 8 + i] = type[i];
            else
                data[index + 8 + i] = '$';
        }
        data[index + 13] = (char) (file.getAttr() + '0');
        data[index + 14] = (char) (file.getInitBlockId() + '0');
        double size = Math.ceil(file.getSize() / (double) DATA.BLOCK_SIZE);
        data[index + 15] = (char) (size > '0' ? size : '1');
    }

    //在父目录磁盘块中修改文件信息
    private void modifyMyFileToParentBlock(MyFile file) {
        Pair<Integer, Integer> coordinate = file.getCoordinate();
        char[] data = blocks[coordinate.getKey()].data;
        Integer index = coordinate.getValue();
        String name = file.getName();
        String type = file.getType();
        char[] nameChar = name.toCharArray();
        char[] typeChar = "$$$$$".toCharArray();
        if (type != null) typeChar = type.toCharArray();
        int attr = file.getAttr();
        for (int i = 0; i < DATA.NAME_MAXLENGTH; i++) {
            if (i < nameChar.length)
                data[index + i] = nameChar[i];
            else
                data[index + i] = '$';
        }
        for (int i = 0; i < DATA.TYPE_MAXLENGTH; i++) {
            if (i < typeChar.length)
                data[index + 8 + i] = typeChar[i];
            else
                data[index + 8 + i] = '$';
        }
        data[index + 13] = (char)(attr+'0');
        //System.out.println("modify： " + attr);
    }

    //更新fat表和磁盘块0，1
    private void updateFat(MyFile file) {
        int index = file.getInitBlockId();
        Block block = blocks[index];
        while (block.next != -1) {
            fat[block.id] = block.next;
            blocks[block.id / DATA.BLOCK_SIZE].data[block.id % DATA.BLOCK_SIZE] = (char) block.next;
            block = blocks[block.next];
        }
        fat[block.id] = block.next;
        blocks[block.id / DATA.BLOCK_SIZE].data[block.id % DATA.BLOCK_SIZE] = (char) block.next;
    }

    // 文件/目录名,类型名 合法性检测
    public RespBean nameValidityTest(String name, String type) {
        if (name.isEmpty()) return RespBean.error("名称不得为空！");
        if (name.length() > DATA.NAME_MAXLENGTH) return RespBean.error("名称不得大于" + DATA.NAME_MAXLENGTH + "位!");
        if (name.matches(".*[$.?*:\"\\/|].*") || name.startsWith(" "))
            return RespBean.error("名称中不能包含 $ . \\ ? * : \" / | ，或者以空格开头！");
        if (type != null) {
            if (type.isEmpty()) return RespBean.error("类型名不得为空！");
            if (type.length() > DATA.TYPE_MAXLENGTH) return RespBean.error("类型名不得大于" + DATA.TYPE_MAXLENGTH + "位！");
            if (type.matches(".*[$.?*:\"\\/|].*") || type.contains(" "))
                return RespBean.error("类型名中不能包含 $ . \\ ? * : \" / | 空格！");
        }
        if (findSameName(name, type) != null) return RespBean.error("已有同名文件/目录!");
        return RespBean.success("√");
    }

    //查找当前目录下是否有同名文件/目录
    public MyFile findSameName(String name, String type) {
        String key = parent.getPath() + "/" + name;
        if (type != null) key += "." + type;
        return fileMap.get(key);
    }

    //创建文件
    public RespBean createFile(String name, String type, int attr) {
//        if (attr % 2 == 1) return RespBean.error("创建文件失败，不能创建包含只读属性的文件!");
        RespBean respBean = nameValidityTest(name, type);
        if (!respBean.isRes()) return respBean;
        int cursor = findFreeNode();
        if (cursor == -1) return RespBean.error("创建文件失败，磁盘空间已满！");
        MyFile file = new MyFile(name, type, attr, cursor, parent);
        blocks[cursor].next = -1;
        updateFat(file);
        RespBean bean = writeMyFileToParentBlock(parent, file);
        if (!bean.isRes()) return bean;
        parent.getChildList().add(file);
        fat[cursor] = DATA.FILE_END;
        blocks[cursor].next = DATA.FILE_END;
        // 磁盘块对应操作
        fileMap.put(file.getPath(), file);
        MyFile myFile = (MyFile) (respBean.getObj());
        return RespBean.success("建立文件成功！", file);
    }

    //打开文件(读)
    public RespBean openFile(String name, String type) {
        MyFile file = findSameName(name, type);
        return openFile(file);
    }

    public RespBean openFile(MyFile file) {
        if (file == null) return RespBean.error("打开失败，目标文件不存在！");
        file.setOpen(true);
        String res = "";
        int index = file.getInitBlockId();
        Block block;
        while (index != -1) {
            block = blocks[index];
            for (int i = 0; i < block.cursor; i++) {
                res += block.data[i];
            }
            index = block.next;
        }
        fileOpened.put(file.getPath(), file);
        return RespBean.success("打开成功", res);
    }

    //写文件(cover为true时覆盖写入，为false时追加写入)
    public RespBean writeFile(String name, String type, String data, boolean cover) {
        MyFile file = findSameName(name, type);
        return writeFile(file, data, cover);
    }

    public RespBean writeFile(MyFile file, String data, boolean cover) {
        if (file == null) return RespBean.error("写文件失败，目标文件不存在！");
        if (!file.isOpen()) return RespBean.error("文件未打开!");
        if (file.getAttr() % 2 == 1) return RespBean.error("只读文件不能写入数据！");
        if (cover) {
            //覆盖写入
            char[] chars = data.toCharArray();
            int init = file.getInitBlockId();
            Block block = blocks[file.getInitBlockId()];
            for (int i = 0; i < chars.length; i += DATA.BLOCK_SIZE) {
                block.cursor = 0;
                for (int j = 0; j < DATA.BLOCK_SIZE && i + j < chars.length; j++) {
                    block.data[j] = chars[i + j];
                    block.cursor++;
                }
                if (i + DATA.BLOCK_SIZE < chars.length) {
                    int index = findFreeNode();
                    block.next = index;
                    block = blocks[index];
                }
            }
            if (block.cursor < DATA.BLOCK_SIZE - 1) {
                block.data[block.cursor] = (char) -1;
            }
            int index = block.next;
            while (index != 0 && index != -1) {
                Block tmp = blocks[index];
                index = tmp.next;
                tmp.next = 0;
                tmp.cursor = 0;
            }
            block.next = DATA.FILE_END;
            file.setSize(chars.length);
        } else {
            //追加写入
            char[] chars = data.toCharArray();
            Block block = blocks[file.getInitBlockId()];
            while (block.next != -1) {
                block = blocks[block.next];
            }
            for (int i = 0; i < chars.length; i++) {
                if (block.cursor >= DATA.BLOCK_SIZE) {
                    int index = findFreeNode();
                    block.next = index;
                    block = blocks[index];
                }
                block.data[block.cursor] = chars[i];
                block.cursor++;
            }
            if (block.cursor < DATA.BLOCK_SIZE - 1) {
                block.data[block.cursor] = (char) -1;
            }
            block.next = DATA.FILE_END;
            file.setSize(file.getSize() + chars.length);
        }
        Pair<Integer, Integer> coordinate = file.getCoordinate();
        double size = Math.ceil(file.getSize() / (double) DATA.BLOCK_SIZE);
        blocks[coordinate.getKey()].data[coordinate.getValue() + 15] = (char) (size > 0 ? size : 1);
        updateFat(file);
        closeFile(file);
        return RespBean.success("写文件成功！");
    }

    //关闭文件
    public void closeFile(String name, String type) {
        MyFile file = findSameName(name, type);
        closeFile(file);
    }

    //关闭文件
    public void closeFile(MyFile file) {
        if (file == null) return;
        if (file.isOpen()) {
            file.setOpen(false);
            fileOpened.remove(file.getPath());
        }
    }

    //删除文件
    public RespBean deleteFile(String name, String type) {
        MyFile file = findSameName(name, type);
        return deleteFile(file);
    }

    public RespBean deleteFile(MyFile file) {

        if (file.isOpen()) return RespBean.error("文件已打开，不能删除！");
        int index = file.getInitBlockId();
        Block block;
        while (index != -1) {
            block = blocks[index];
            fat[block.id] = 0;
            blocks[block.id / DATA.BLOCK_SIZE].data[block.id % DATA.BLOCK_SIZE] = 0;
            block.cursor = 0;
            index = block.next;
            block.next = 0;
        }
        //磁盘块对应操作
        Pair<Integer, Integer> coordinate = file.getCoordinate();
        blocks[coordinate.getKey()].data[coordinate.getValue()] = '$';

        file.getParent().getChildList().remove(file);
        fileMap.remove(file.getPath());
        return RespBean.success("删除成功");
    }

    //查看文件信息
    public RespBean infoFile(String name, String type) {
        MyFile file = findSameName(name, type);
        if (file == null) return RespBean.error("查看失败，目标文件不存在！");
        if (file.isOpen()) return RespBean.error("文件已打开，不能查看信息！");
        return RespBean.success("文件信息", file);
    }

    //修改文件信息
    public RespBean modifyFile(String name, String type, String newName, String newType, int newAttr) {
        MyFile file = findSameName(name, type);
        return modifyFile(file, newName, newType, newAttr);
    }

    public RespBean modifyFile(MyFile file, String newName, String newType, int newAttr) {
        if (file == null) return RespBean.error("修改失败，目标文件不存在！");
        if (file.isOpen()) return RespBean.error("文件已打开，不能修改信息！");
        RespBean respBean = nameValidityTest(newName, newType);
        if (!respBean.isRes() && (!file.getName().equals(newName) || !file.getType().equals(newType))) return respBean;
        fileMap.remove(file.getPath());
        file.setName(newName);
        file.setType(newType);
        file.setAttr(newAttr);
        file.setPath(parent.getPath() + "/" + newName + "." + newType);
        //磁盘块对应操作
        modifyMyFileToParentBlock(file);
        fileMap.put(file.getPath(), file);
        return RespBean.success("修改成功", file);
    }

    //建立目录
    public RespBean createDirectory(String name) {
        RespBean respBean = nameValidityTest(name, null);
        if (!respBean.isRes()) return respBean;
        int cursor = findFreeNode();
        if (cursor == -1) return RespBean.error("创建目录失败，磁盘空间已满！");
        MyFile directory = new MyFile(name, cursor, parent);
        blocks[cursor].next = -1;
        updateFat(directory);
        for(int i=0;i<DATA.BLOCK_SIZE;i+=16)
            blocks[cursor].data[i] = '$';
        RespBean bean = writeMyFileToParentBlock(parent, directory);
        if (!bean.isRes()) return bean;
        parent.getChildList().add(directory);
        fat[cursor] = DATA.FILE_END;
        blocks[cursor].next = DATA.FILE_END;
        //todo 磁盘块对应操作
        fileMap.put(directory.getPath(), directory);
        return RespBean.success("创建目录成功！", directory);
    }

    //打开目录
    public RespBean openDirectory(String name) {
        MyFile directory = findSameName(name, null);
        return openDirectory(directory);
    }

    public RespBean openDirectory(MyFile directory) {
        if (directory == null) return RespBean.error("打开失败，目标目录不存在");
        parent = directory;
        return RespBean.success("打开成功", directory);
    }

    //修改目录
    public RespBean modifyDirectory(String name, String newName) {
        MyFile directory = findSameName(name, null);
        return modifyDirectory(directory, newName);
    }

    public RespBean modifyDirectory(MyFile directory, String newName) {
        if (directory == null) return RespBean.error("该目录不存在！");
        RespBean respBean = nameValidityTest(newName, null);
        if (!respBean.isRes()) return respBean;
        fileMap.remove(directory.getPath());
        directory.setName(newName);
        directory.setPath(parent.getPath() + "/" + newName);
        modifyMyFileToParentBlock(directory);
        fileMap.put(directory.getPath(), directory);
        return RespBean.success("修改成功！", directory);
    }

    //删除空目录
    public RespBean deleteDirectory(String name, String type) {
        MyFile directory = findSameName(name, type);
        return deleteDirectory(directory);
    }

    public RespBean deleteDirectory(MyFile directory) {
        if (directory == null) return RespBean.error("删除失败，目标目录不存在！");
        if (directory.getParent() == null) return RespBean.error("不能删除根目录！");
        if (directory.getChildList().size() != 0) return RespBean.error("目录为非空目录，不能删除！");

        int index = directory.getInitBlockId();
        Block block;
        while (index != -1) {
            block = blocks[index];
            fat[block.id] = 0;
            blocks[block.id / DATA.BLOCK_SIZE].data[block.id % DATA.BLOCK_SIZE] = 0;
            index = block.next;
            block.next = 0;
        }

        directory.getParent().getChildList().remove(directory);
        //磁盘块对应操作
        Pair<Integer, Integer> coordinate = directory.getCoordinate();
        blocks[coordinate.getKey()].data[coordinate.getValue()] = '$';

        fileMap.remove(directory.getPath());
        return RespBean.success("删除成功");
    }

}
